package cn.zhangtiancinb.simple_demo.exception_switch.client;

import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.Collection;

import lombok.Data;
import cn.hutool.setting.Setting;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.nosql.redis.RedisDS;

/**
 * 异常开关工具类
 *
 * @author 张天赐
 */
@Data
@Slf4j
public class ExceptionSwitchUtil {
    private static final String KEYS_SUFFIX = "_KEYS";
    /**
     * REDIS操作器
     */
    private RedisDS redisDataSource;
    /**
     * 配置实例
     */
    private ExceptionSwitchConfig config;
    /**
     * 共用实例
     */
    private static ExceptionSwitchUtil util;

    /**
     * 私有化的构造函数
     */
    private ExceptionSwitchUtil() {}

    /**
     * 开放的实例化方法
     *
     * @return 工具类实例
     */
    public static ExceptionSwitchUtil getInstance(Map<String, Object> config) {
        if (util == null) {
            synchronized (ExceptionSwitchUtil.class) {
                if (util == null) {
                    // 配置一下
                    Setting setting = Setting.create();
                    config.forEach((k, v) -> {
                        if (v != null) {
                            setting.set(k, v.toString());
                            log.info("{}配置为{}", k, v);
                        } else {
                            log.warn("{}值为空,跳过配置", k);
                        }
                    });

                    util = new ExceptionSwitchUtil() {{
                        setConfig(new ExceptionSwitchConfig() {{
                            setStorageKey(setting.getStr("storage_key", "exception_switch_redis"));
                            setDefaultAcuteSlowTimeout(setting.getLong("default_acute_slow", 800L));
                            setDefaultChronicSlowTimeout(setting.getLong("default_chronic_slow", 300L));
                        }});
                        setRedisDataSource(RedisDS.create(setting, null));
                    }};
                }
                return util;
            }
        } else {
            return util;
        }
    }

    /**
     * 读取所有开关
     *
     * @return 所有开关信息
     */
    public Map<String, Boolean> read() {
        HashMap<String, Boolean> result = new HashMap<>(0);
        try (Jedis jedis = redisDataSource.getJedis()) {
            Map<String, String> all = jedis.hgetAll(config.getStorageKey());
            all.forEach((k, v) -> result.put(k, "1".equals(v)));
            return result;
        } catch (Exception e) {
            log.error("读取所有开关出错", e);
            throw new RuntimeException("读取所有开关出错", e);
        }
    }

    /**
     * 读取所有开关状态为开启的键
     *
     * @return 所有开关信息
     */
    private Map<String, Boolean> readEnabled() {
        Map<String, Boolean> result = new HashMap<>(0);
        Map<String, Boolean> allData = read();
        allData.forEach((key, value) -> {
            if (value) { result.put(key, true); }
        });
        return result;
    }

    /**
     * 写入开关状态
     *
     * @param input 待写入数据
     */
    public void write(Map<String, Boolean> input) {
        try (Jedis jedis = redisDataSource.getJedis()) {
            // 删除旧的KEY
            {
                Set<String> oldKeys = jedis.hkeys(config.getStorageKey());
                String[] readyDeleteKeys = oldKeys.stream().filter(t -> !input.containsKey(t)).toArray(String[]::new);
                if (readyDeleteKeys.length > 0) { jedis.hdel(config.getStorageKey(), readyDeleteKeys);}
            }
            //写入新的KEY
            {
                if (input.keySet().size() == 0) {return;}
                Map<String, String> data = new HashMap<>(input.size());
                input.forEach((k, v) -> data.put(k, v != null && v ? "1" : ""));
                jedis.hmset(config.getStorageKey(), data);
            }
        } catch (Exception e) {
            log.error("写入开关状态出错", e);
            throw new RuntimeException("写入开关状态出错", e);
        }
    }

    /**
     * 检查Key的开关启用状态
     *
     * @param key 要检查的Key
     * @return 开/关
     */
    public boolean enabled(String key) {
        try (Jedis jedis = redisDataSource.getJedis()) {
            jedis.sadd(config.getStorageKey() + KEYS_SUFFIX, key);
        } catch (Exception e) { log.error("填充KEY失败", e); }
        Map<String, Boolean> result = readEnabled();
        Boolean value = result.getOrDefault(key, false);
        return value != null && value;
    }

    /**
     * 获取所有运行过的KEY
     *
     * @return KEY集合
     */
    public Collection<String> keys() {
        try (Jedis jedis = redisDataSource.getJedis()) {
            return jedis.smembers(config.getStorageKey() + KEYS_SUFFIX);
        } catch (Exception e) {
            log.error("获取所有的KEY出错", e);
            throw new RuntimeException("获取所有的KEY出错", e);
        }
    }

    /**
     * 异常的快捷调用
     *
     * @param key 要检查的Key
     */
    private void exception(String key) {
        if (enabled(StrUtil.format("{}异常{}", key, ""))) {
            throw new ExceptionSwitchException();
        }
    }

    /**
     * 卡慢的快捷调用 一般
     *
     * @param key 要检查的Key
     * @return 调用结果
     */
    private boolean chronicTimeout(String key) {
        return timeout(key, config.getDefaultChronicSlowTimeout());
    }

    /**
     * 卡慢的快捷调用 严重
     *
     * @param key 要检查的Key
     * @return 调用结果
     */
    private boolean acuteTimeout(String key) {
        return timeout(key, config.getDefaultAcuteSlowTimeout());
    }

    /**
     * 超时
     *
     * @param key         要检查的Key
     * @param millisecond 超时的毫秒数
     */
    public boolean timeout(String key, long millisecond) {
        if (enabled(StrUtil.format("{}卡慢{}毫秒", key, millisecond))) {
            try {
                Thread.sleep(millisecond);
                return true;
            } catch (InterruptedException e) {
                log.error("手动卡慢失败", e);
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 快捷执行
     *
     * @param key 执行KEY
     */
    public void callQuickly(String key) {
        // 先严重异常
        if (!acuteTimeout(key)) {
            // 如果没执行成功 执行一般异常
            if (!chronicTimeout(key)) {
                log.info("快捷触发.{}卡慢均未执行", key);
            }
        }
        exception(key);
    }

    /**
     * 定制执行
     *
     * @param key         执行KEY
     * @param millisecond 卡慢时长
     */
    public void call(String key, long millisecond) {
        timeout(key, millisecond);
        exception(key);
    }
}
